// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jshin

#pragma once

#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/bbo_container.h"
#include "fastfeature/feature_feed.pb.h"

namespace coin2::base {

namespace impl {

using coin2::exchange::base::feed::FeedUpdate;

inline bool ConvertBookMessage(const IBook& book, int n, ::fastfeature::BookMessage* bm, double qty_multiplier = 1) {
  {
    auto* side = bm->mutable_bid();
    side->Reserve(n);
    int32_t i = 0;
    auto iter = book.BidBegin();
    auto end = book.BidEnd();
    while (i < n && iter != end) {
      const auto& level = *iter;
      auto* pentry = side->Add();
      pentry->set_price_float(level.first);
      pentry->set_qty_float(level.second * qty_multiplier);
      ++i;
      ++iter;
    }
  }

  {
    auto* side = bm->mutable_ask();
    side->Reserve(n);
    int32_t i = 0;
    auto iter = book.AskBegin();
    auto end = book.AskEnd();
    while (i < n && iter != end) {
      const auto& level = *iter;
      auto* pentry = side->Add();
      pentry->set_price_float(level.first);
      pentry->set_qty_float(level.second * qty_multiplier);
      ++i;
      ++iter;
    }
  }
  return true;
}

inline ::fastfeature::TradeSide side_convert(
    coin2::exchange::feed::TradeSide coin2_side) {
  switch (coin2_side) {
    case coin2::exchange::feed::TRADE_BUY_SIDE:
      return ::fastfeature::TRADE_BUY_SIDE;
    case coin2::exchange::feed::TRADE_SELL_SIDE:
      return ::fastfeature::TRADE_SELL_SIDE;
    case coin2::exchange::feed::TRADE_NO_SIDE:
      return ::fastfeature::TRADE_NO_SIDE;
    default:
      CHECK(false) << coin2::exchange::feed::TradeSide_Name(coin2_side);
      return ::fastfeature::TRADE_NO_SIDE;
  }
}

inline ::fastfeature::TradeSide sign_convert(double sign) {
  if (sign == 1) {
    return ::fastfeature::TRADE_BUY_SIDE;
  } else if (sign == -1) {
    return ::fastfeature::TRADE_SELL_SIDE;
  } else {
    CHECK(false) << sign;
    return ::fastfeature::TRADE_NO_SIDE;
  }
}

inline bool ConvertTradeMessage(const Trade& trade, ::fastfeature::TradeMessage* tm, double qty_multiplier = 1) {
  tm->set_price_float(trade.price);
  tm->set_qty_float(trade.fill_qty * qty_multiplier);
  tm->set_side(side_convert(trade.side));
  tm->set_has_more_trade(trade.has_more_trade);
  return true;
}

inline bool ConvertLiquidationMessage(
    const LiquidationOrder& liquidation, ::fastfeature::LiquidationMessage* lm) {
  lm->set_price_float(liquidation.price);
  lm->set_fill_qty_float(liquidation.fill_qty);
  lm->set_remain_qty_float(liquidation.qty - liquidation.fill_qty);
  lm->set_side(sign_convert(liquidation.sign()));
  lm->set_has_more_liquidation(liquidation.has_more_liquidation_order);
  return true;
}

inline bool ConvertFundingRateMessage(
    const FundingRate& funding_rate, ::fastfeature::FundingRateMessage* fm) {
  fm->set_funding_rate(funding_rate.funding_rate);
  fm->set_estimated_rate(funding_rate.estimated_rate);
  fm->set_funding_time(funding_rate.funding_time);
  return true;
}

inline bool ConvertTopLongShortAccountMessage(
    const TradingData<TopLongShortAccountRatio>& top_long_short_account_ratio, ::fastfeature::TopLongShortAccountRatioMessage* fm) {
  fm->set_long_short_ratio(top_long_short_account_ratio.Get().value()->long_short_ratio);
  fm->set_long_account(top_long_short_account_ratio.Get().value()->long_account);
  fm->set_short_account(top_long_short_account_ratio.Get().value()->short_account);
  return true;
}

inline bool ConvertTopLongShortPositionMessage(
    const TradingData<TopLongShortPositionRatio>& top_long_short_position_ratio, ::fastfeature::TopLongShortPositionRatioMessage* fm) {
  fm->set_long_short_ratio(top_long_short_position_ratio.Get().value()->long_short_ratio);
  fm->set_long_position(top_long_short_position_ratio.Get().value()->long_position);
  fm->set_short_position(top_long_short_position_ratio.Get().value()->short_position);
  return true;
}

inline bool ConvertGlobalLongShortAccountMessage(
    const TradingData<GlobalLongShortAccountRatio>& global_long_short_account_ratio, ::fastfeature::GlobalLongShortAccountRatioMessage* fm) {
  fm->set_long_short_ratio(global_long_short_account_ratio.Get().value()->long_short_ratio);
  fm->set_long_account(global_long_short_account_ratio.Get().value()->long_account);
  fm->set_short_account(global_long_short_account_ratio.Get().value()->short_account);
  return true;
}

inline bool ConvertFeedMessage(
    const FeedUpdate& upd,
    int num_of_level,
    ::fastfeature::FeedMessage* fm,
    double qty_multiplier = 1) {
  fm->Clear();

  fm->set_fetched_time(upd.timestamp());

  const auto* bbo_est = upd.GetBboEst();
  if (bbo_est) {
    auto* bbo_est_proto = fm->mutable_bbo_est();
    bbo_est_proto->set_ask_price(bbo_est->ask_price);
    bbo_est_proto->set_bid_price(bbo_est->bid_price);
  }

  int64_t exchange_timestamp = 0L;
  if (upd.is_book()) {
    fm->set_feed_type(::fastfeature::BOOK_FEED);
    ConvertBookMessage(
        upd.book(),
        num_of_level,
        fm->mutable_book(),
        qty_multiplier);
    exchange_timestamp = upd.book().Timestamp();
  } else if (upd.is_trade()) {
    fm->set_feed_type(::fastfeature::TRADE_FEED);
    ConvertTradeMessage(
        upd.trade(),
        fm->mutable_trade(),
        qty_multiplier);
    exchange_timestamp = upd.trade().timestamp;
  } else if (upd.is_liquidation()) {
    fm->set_feed_type(::fastfeature::STATUS_FEED);
    ConvertLiquidationMessage(upd.liquidation_order(), fm->mutable_liquidation());
    exchange_timestamp = upd.liquidation_order().timestamp;
  } else if (upd.is_index()) {
    fm->set_feed_type(::fastfeature::STATUS_FEED);
    fm->mutable_index()->set_price_float(upd.index().price);
    exchange_timestamp = upd.index().timestamp;
  } else if (upd.is_funding_rate()) {
    fm->set_feed_type(::fastfeature::STATUS_FEED);
    ConvertFundingRateMessage(upd.funding_rate(), fm->mutable_funding_rate());
    exchange_timestamp = upd.funding_rate().timestamp;
  } else if (upd.is_open_interest()) {
    fm->set_feed_type(::fastfeature::STATUS_FEED);
    fm->mutable_open_interest()->set_qty_float(upd.open_interest().open_interest_qty);
    exchange_timestamp = upd.open_interest().timestamp;
  } else if (upd.is_wallet()) {
    fm->set_feed_type(::fastfeature::STATUS_FEED);
    fm->mutable_wallet()->set_can_deposit(upd.wallet().can_deposit);
    fm->mutable_wallet()->set_can_withdraw(upd.wallet().can_withdraw);
    exchange_timestamp = upd.wallet().timestamp;
  } else if (upd.is_top_long_short_account_ratio()) {
    fm->set_feed_type(::fastfeature::STATUS_FEED);
    ConvertTopLongShortAccountMessage(upd.top_long_short_account_ratio(), fm->mutable_top_long_short_account_ratio());
    exchange_timestamp = upd.wallet().timestamp;
  } else if (upd.is_top_long_short_position_ratio()) {
    fm->set_feed_type(::fastfeature::STATUS_FEED);
    ConvertTopLongShortPositionMessage(upd.top_long_short_position_ratio(), fm->mutable_top_long_short_position_ratio());
    exchange_timestamp = upd.wallet().timestamp;
  } else if (upd.is_global_long_short_account_ratio()) {
    fm->set_feed_type(::fastfeature::STATUS_FEED);
    ConvertGlobalLongShortAccountMessage(upd.global_long_short_account_ratio(), fm->mutable_global_long_short_account_ratio());
    exchange_timestamp = upd.wallet().timestamp;
  }
  if (upd.klines().Size() > 0) {
    auto* kline = fm->mutable_kline();
    auto* symkline = kline->add_klines();

    auto& klines = upd.klines();
    for (auto iter = klines.RBegin(); iter != klines.REnd(); ++iter) {
      auto* pkline = symkline->add_ohlc();
      pkline->set_kline_timestamp(iter->open_timestamp);
      pkline->set_open(iter->open);
      pkline->set_close(iter->close);
      pkline->set_high(iter->high);
      pkline->set_low(iter->low);
      pkline->set_volume(iter->volume);
    }
  }
  if (exchange_timestamp > 0) {
    fm->set_exchange_time(exchange_timestamp);
  }

  return true;
}

}  // namespace impl

using impl::ConvertBookMessage;
using impl::ConvertFeedMessage;
using impl::ConvertTradeMessage;

}  // namespace coin2::base
